#ifndef SL_BASE_HPP
#define SL_BASE_HPP

// std Header
#include <string>

namespace sl
{
	static const std::string base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ""abcdefghijklmnopqrstuvwxyz""0123456789+/";
	static const std::string base16 = "0123456789ABCDEF";

	template <unsigned int B> bool IsBase(unsigned char c)
	{
		if (B <= 16)
		{
			for (unsigned int i = 0; i < base16.size(); i++)
				if (c == base16[i])
					return true;
		}
		else if (B < 63)
		{
			return isalnum(c);
		}
		else if (B >= 63 && B <= 64)
		{
			return (isalnum(c) || (c == '+') || (c == '/'));
		}

		return false;
	}

	template <unsigned int B> std::string Decode(const std::string& text)
	{
		unsigned int len = text.size();
		unsigned int i = 0, j = 0;
		unsigned int in_ = 0;
		unsigned char char_array_4[4], char_array_3[3];
		std::string ret;

		while (len-- && (text[in_] != '=') && IsBase<B>(text[in_]))
		{
			char_array_4[i++] = text[in_];
			in_++;

			if (i == 4)
			{
				for (i = 0; i < 4; i++)
					char_array_4[i] = base64.find(char_array_4[i]);

				char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
				char_array_3[1] = ((char_array_4[1] & 0xF) << 4) + ((char_array_4[2] & 0x3C) >> 2);
				char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

				for (i = 0; i < 3; i++)
					ret += char_array_3[i];

				i = 0;
			}
		}

		if (i)
		{
			for (j = i; j < 4; j++)
				char_array_4[j] = 0;

			for (j = 0; j < 4; j++)
				char_array_4[j] = base64.find(char_array_4[j]);

			char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
			char_array_3[1] = ((char_array_4[1] & 0xF) << 4) + ((char_array_4[2] & 0x3C) >> 2);
			char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

			for (j = 0; j < i - 1; j++)
				ret += char_array_3[j];
		}

		return ret;
	}

	namespace tmp
	{
		template <unsigned int B> std::string Encode(unsigned char const* bytes_to_encode, unsigned int in_len)
		{
			std::string ret;
			unsigned int i = 0, j = 0;
			unsigned char char_array_3[3];
			unsigned char char_array_4[4];

			while (in_len--)
			{
				char_array_3[i++] = *(bytes_to_encode++);
				if (i == 3)
				{
					char_array_4[0] = (char_array_3[0] & 0xFC) >> 2;
					char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xF0) >> 4);
					char_array_4[2] = ((char_array_3[1] & 0x0F) << 2) + ((char_array_3[2] & 0xC0) >> 6);
					char_array_4[3] = char_array_3[2] & 0x3F;

					for (i = 0; i < 4; i++)
						ret += base64[char_array_4[i]];

					i = 0;
				}
			}

			if (i)
			{
				for (j = i; j < 3; j++)
					char_array_3[j] = '\0';

				char_array_4[0] = (char_array_3[0] & 0xFC) >> 2;
				char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xF0) >> 4);
				char_array_4[2] = ((char_array_3[1] & 0x0F) << 2) + ((char_array_3[2] & 0xC0) >> 6);
				char_array_4[3] = char_array_3[2] & 0x3F;

				for (j = 0; j < i + 1; j++)
					ret += base64[char_array_4[j]];

				while (i++ < 3)
					ret += '=';
			}

			return ret;
		}
	}

	template <unsigned int B> std::string Encode(const std::string& text)
	{
		return tmp::Encode<64>(reinterpret_cast<const unsigned char*>(text.c_str()), text.length());
	}
}

#endif //SL_BASE64_HPP